package top.fullj.rx;

import io.reactivex.Maybe;
import io.reactivex.Observable;
import io.reactivex.Single;
import io.reactivex.annotations.NonNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import top.fullj.func.Supplier;
import top.fullj.spi.DiscoverException;

import java.util.Iterator;
import java.util.ServiceLoader;

/**
 * @author bruce.wu
 * @since 2021/9/18 20:20
 */
public abstract class RxDiscover {

    private static final Logger LOG = LoggerFactory.getLogger(RxDiscover.class);

    public static <T> Single<T> just(@NonNull Class<T> type) {
        Iterator<T> it = ServiceLoader.load(type).iterator();
        if (!it.hasNext()) {
            return Single.error(new DiscoverException("no service: " + type.getName()));
        }
        T instance = it.next();
        if (it.hasNext()) {
            return Single.error(new DiscoverException("another service: " + it.next().getClass().getName()));
        }
        LOG.debug("load: {}[{}]", type.getName(), instance.getClass().getName());
        return Single.just(instance);
    }

    public static <T> Single<T> first(@NonNull Class<T> type) {
        Iterator<T> it = ServiceLoader.load(type).iterator();
        if (it.hasNext()) {
            T instance = it.next();
            LOG.debug("load: {}[{}]", type.getName(), instance.getClass().getName());
            return Single.just(instance);
        }
        return Single.error(new DiscoverException("no service: " + type.getName()));
    }

    public static <T> Single<T> firstOr(@NonNull Class<T> type, Supplier<T> supplier) {
        Iterator<T> it = ServiceLoader.load(type).iterator();
        T instance = it.hasNext() ? it.next() : supplier.get();
        LOG.debug("load: {}[{}]", type.getName(), instance.getClass().getName());
        return Single.just(instance);
    }

    public static <T> Maybe<T> maybe(@NonNull Class<T> type) {
        Iterator<T> it = ServiceLoader.load(type).iterator();
        if (it.hasNext()) {
            T instance = it.next();
            LOG.debug("load: {}[{}]", type.getName(), instance.getClass().getName());
            return Maybe.just(instance);
        }
        LOG.debug("load: {}[empty]", type.getName());
        return Maybe.empty();
    }

    public static <T> Observable<T> each(@NonNull Class<T> type) {
        return Observable.create(emitter -> {
            for (T instance : ServiceLoader.load(type)) {
                LOG.debug("load: {}[{}]", type.getName(), instance.getClass().getName());
                emitter.onNext(instance);
            }
            emitter.onComplete();
        });
    }

}
